﻿using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
using DevExpress.Mvvm.POCO;
using DotNetExtensions.AspNet.DevExpress.Services;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;

namespace DotNetExtensions.AspNet.DevExpress.MVVM.ViewModels
{
    [POCOViewModel]
    public abstract class SingleObjectViewModelBase<TEntity, TPrimaryKey> : ISingleObjectViewModel<TEntity, TPrimaryKey>, ISupportParameter, IDocumentContent, ISupportLogicalLayout<TPrimaryKey>
         where TEntity : class
    {
        protected TPrimaryKey PrimaryKey { get; private set; }
        protected IMessageBoxService MessageBoxService { get { return this.GetRequiredService<IMessageBoxService>(); } }
        protected ILayoutSerializationService LayoutSerializationService { get { return this.GetService<ILayoutSerializationService>(); } }
        protected IValidationService ValidationService { get { return this.GetService<IValidationService>(); } }
        protected IMessageService MessageService { get { return this.GetService<IMessageService>(); } }

        object title;
        protected readonly Func<TEntity, object> getEntityDisplayNameFunc;
        Action<TEntity> entityInitializer;
        bool isEntityNewAndUnmodified;
        readonly Dictionary<string, IDocumentContent> lookUpViewModels = new Dictionary<string, IDocumentContent>();
        protected IDocumentOwner DocumentOwner { get; private set; }
        protected SingleObjectViewModelBase(Func<TEntity, object> getEntityDisplayNameFunc)
        {
            this.getEntityDisplayNameFunc = getEntityDisplayNameFunc;

            if (this.IsInDesignMode())
                this.Entity = null;
            else
                OnInitializeInRuntime();
        }

        public object Title { get { return title; } }


        public virtual TEntity Entity { get; protected set; }

        /// </summary>
        [Display(AutoGenerateField = false)]
        public void Update()
        {
            isEntityNewAndUnmodified = false;
            UpdateTitle();
            UpdateCommands();
        }


        public virtual void Save()
        {
            SaveCore();
        }


        public virtual bool CanSave()
        {
            return true;
        }

        public virtual bool Validate()
        {
            return ValidationService.Validate();
        }
        public virtual bool BeforeClose(out string message)
        {
            message = ""; return true;
        }

        [Command(CanExecuteMethodName = "CanSave")]
        public void SaveAndClose()
        {
            if (SaveCore()) Close();
        }


        [Command(CanExecuteMethodName = "CanSave")]
        public void SaveAndNew()
        {
            if (SaveCore())
                CreateAndInitializeEntity(this.entityInitializer);
        }

        [Display(Name = "Reset Changes")]
        public void Reset()
        {
            Reload();
        }

        public bool CanReset()
        {
            return NeedReset();
        }

        string ViewName { get { return typeof(TEntity).Name + "View"; } }

        [DXImage("Save")]
        [Display(Name = "Save Layout")]
        public void SaveLayout()
        {
            PersistentLayoutHelper.TrySerializeLayout(LayoutSerializationService, ViewName);
            PersistentLayoutHelper.SaveLayout();
        }

        [Display(AutoGenerateField = false)]
        public void OnLoaded()
        {
            PersistentLayoutHelper.TryDeserializeLayout(LayoutSerializationService, ViewName);
        }

        public virtual void Delete()
        {

        }

        public virtual bool CanDelete()
        {
            return Entity != null && !IsNew();
        }
        public void Close()
        {
            if (TryClose() && DocumentOwner != null) DocumentOwner.Close(this);
        }

        public void SilenceClose()
        {
            if (DocumentOwner != null) DocumentOwner.Close(this);
        }

        public void NavigateTo(string viewName, bool closePrevDocuments = false)
        {
            Messenger.Default.Send(new FlyoutDocumentOption(viewName, closePrevDocuments));
        }

        public void CloseAll()
        {
            Messenger.Default.Send(new FlyoutDocumentOption(1));
        }

        public virtual void Readying() { }

        protected virtual bool SaveCore()
        {
            return true;
        }

        protected virtual void OnBeforeEntitySaved(TPrimaryKey primaryKey, TEntity entity, bool isNewEntity) { }

        protected virtual void OnEntitySaved(TPrimaryKey primaryKey, TEntity entity, bool isNewEntity)
        {
            Messenger.Default.Send(new EntityMessage<TEntity, TPrimaryKey>(primaryKey, isNewEntity ? EntityMessageType.Added : EntityMessageType.Changed));
        }

        protected virtual void OnBeforeEntityDeleted(TPrimaryKey primaryKey, TEntity entity) { }

        protected virtual void OnEntityDeleted(TPrimaryKey primaryKey, TEntity entity)
        {
            Messenger.Default.Send(new EntityMessage<TEntity, TPrimaryKey>(primaryKey, EntityMessageType.Deleted));
        }

        protected virtual void OnInitializeInRuntime()
        {
            Messenger.Default.Register<EntityMessage<TEntity, TPrimaryKey>>(this, x => OnEntityMessage(x));
            Messenger.Default.Register<SaveAllMessage>(this, x => Save());
            Messenger.Default.Register<CloseAllMessage>(this, x => OnClosing(x));
        }

        protected virtual void OnEntityMessage(EntityMessage<TEntity, TPrimaryKey> message)
        {
            if (Entity == null) return;
            if (message.MessageType == EntityMessageType.Deleted && object.Equals(message.PrimaryKey, PrimaryKey))
                Close();
        }

        protected virtual void OnEntityChanged()
        {
            Update();
        }

        
        protected virtual void OnParameterChanged(object parameter)
        {
            var initializer = parameter as Action<TEntity>;
            if (initializer != null)
                CreateAndInitializeEntity(initializer);
            else if (parameter is TPrimaryKey)
            { }
            else
                Entity = null;
        }
        protected void Reload()
        {
        }

        protected void CreateAndInitializeEntity(Action<TEntity> entityInitializer)
        {
            this.entityInitializer = entityInitializer;
            isEntityNewAndUnmodified = true;
        }


        void UpdateTitle()
        {
            this.RaisePropertyChanged(x => x.Title);
        }

        protected virtual void UpdateCommands()
        {
            this.RaiseCanExecuteChanged(x => x.Save());
            this.RaiseCanExecuteChanged(x => x.SaveAndClose());
            this.RaiseCanExecuteChanged(x => x.SaveAndNew());
            this.RaiseCanExecuteChanged(x => x.Delete());
            this.RaiseCanExecuteChanged(x => x.Reset());
        }

        

        protected virtual void OnDestroy()
        {
            Messenger.Default.Unregister(this);
            RefreshLookUpCollections(false);
        }

        protected virtual bool TryClose()
        {
            string message;
            if (!BeforeClose(out message))
            {
                MessageResult warningResult = MessageBoxService.ShowMessage(message, "Confirm", MessageButton.OKCancel);
                return warningResult == MessageResult.OK;
            }

            return true;
        }

        protected virtual void OnClosing(CloseAllMessage message)
        {
            message.Cancel = !TryClose();
        }

        protected virtual string GetConfirmationMessageTitle()
        {
            return GetTitle();
        }

        public bool IsNew()
        {
            return true;
        }

        protected virtual bool NeedSave()
        {
            return true;
        }

        protected virtual bool NeedReset()
        {
            return NeedSave() && !isEntityNewAndUnmodified;
        }

        protected virtual bool HasValidationErrors()
        {
            IDataErrorInfo dataErrorInfo = Entity as IDataErrorInfo;
            return dataErrorInfo != null && IDataErrorInfoHelper.HasErrors(dataErrorInfo);
        }

        string GetTitle(bool entityModified)
        {
            return GetTitle();
        }

        protected virtual string GetTitleForNewEntity()
        {
            return typeof(TEntity).Name;
        }

        protected virtual string GetTitle()
        {
            return (typeof(TEntity).Name + " - " + Convert.ToString(getEntityDisplayNameFunc != null ? getEntityDisplayNameFunc(Entity) : PrimaryKey))
            .Split(new string[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
        }

        #region look up and detail view models
        protected virtual void RefreshLookUpCollections(bool raisePropertyChanged)
        {
            var values = lookUpViewModels.ToArray();
            lookUpViewModels.Clear();
            foreach (var item in values)
            {
                item.Value.OnDestroy();
                if (raisePropertyChanged)
                    ((IPOCOViewModel)this).RaisePropertyChanged(item.Key);
            }
        }
        Action<TDetailEntity> CreateForeignKeyPropertyInitializer<TDetailEntity, TForeignKey>(Action<TDetailEntity, TPrimaryKey> setMasterEntityKeyAction, Func<TForeignKey> getMasterEntityKey) where TDetailEntity : class
        {
            return x => setMasterEntityKeyAction(x, (TPrimaryKey)(object)getMasterEntityKey());
        }

        #endregion

        #region ISupportParameter
        object ISupportParameter.Parameter
        {
            get { return null; }
            set { OnParameterChanged(value); }
        }
        #endregion

        #region IDocumentContent
        object IDocumentContent.Title { get { return Title; } }

        void IDocumentContent.OnClose(CancelEventArgs e)
        {
            e.Cancel = !TryClose();
            Messenger.Default.Send(new DestroyOrphanedDocumentsMessage());
        }

        void IDocumentContent.OnDestroy()
        {
            OnDestroy();
        }

        IDocumentOwner IDocumentContent.DocumentOwner
        {
            get { return DocumentOwner; }
            set
            {
                DocumentOwner = value;
            }
        }
        #endregion

        #region ISingleObjectViewModel
        TEntity ISingleObjectViewModel<TEntity, TPrimaryKey>.Entity { get { return Entity; } }

        TPrimaryKey ISingleObjectViewModel<TEntity, TPrimaryKey>.PrimaryKey { get { return PrimaryKey; } }
        #endregion

        #region ISupportLogicalLayout
        bool ISupportLogicalLayout.CanSerialize
        {
            get { return Entity != null && !IsNew(); }
        }

        TPrimaryKey ISupportLogicalLayout<TPrimaryKey>.SaveState()
        {
            return PrimaryKey;
        }

        void ISupportLogicalLayout<TPrimaryKey>.RestoreState(TPrimaryKey key)
        {

        }

        IDocumentManagerService ISupportLogicalLayout.DocumentManagerService
        {
            get { return this.GetService<IDocumentManagerService>(); }
        }

        IEnumerable<object> ISupportLogicalLayout.LookupViewModels
        {
            get { return lookUpViewModels.Values; }
        }
        #endregion
    }
}
